package org.osll.tictactoe.engine;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Vector;


import org.osll.tictactoe.CurrentMove;
import org.osll.tictactoe.Field;
import org.osll.tictactoe.Move;
import org.osll.tictactoe.Team;
import org.osll.tictactoe.game.Game;
import org.osll.tictactoe.game.Transport;



/**
 * Реализация игра в крестики нолики
 */
public class GameImpl implements Game {
	
	private FieldImpl field = null;
	
	private Vector<Move> moves = new Vector<Move>(); // история ходов
	
	private int reflex = 100000;
	
	private HashMap<Team, String> users = new HashMap<Team, String>();
	private HashMap<String, Boolean> logged_users = new HashMap<String, Boolean>();
	
	private HashMap<Team, Date> times = new HashMap<Team, Date>();
	
	private CurrentMove currentMove = null;
	
	private Transport transport = new TransportImpl(this);

	private boolean started = false;
	private boolean over = false;
	
	public GameImpl(int rows, int cols, int reflexTime) {
		field = new FieldImpl(rows,cols);
		reflex = reflexTime;
		times.put(Team.X, new Date(0));
		times.put(Team.O, new Date(0));
	}
	
	@Override
	synchronized public void makeMove(Move move) throws GameOverException {
		Date now = new Date();
		if(getCurrentMove().getTeam() != move.getTeam())
			throw new IllegalStateException("Повторный ход невозможен.");
		
		moves.add(move);
		field.addMove(move);
		checkOver(move,1);
		times.put(move.getTeam(), now);
		if(move.getTeam() == Team.X) {
			currentMove = new CurrentMoveImpl(Team.O, new Date(now.getTime() + reflex) );
		} else {
			currentMove = new CurrentMoveImpl(Team.X, new Date(now.getTime() + reflex) );
		}
	}
	
	private class Coord {
		private int row;
		public int getCol() {
			return col;
		}

		public int getRow() {
			return row;
		}

		private int col;
		public Coord(int row, int col) {
			this.row = row;
			this.col = col;
		}
		
		@Override
		public boolean equals(Object o)
		{
			if(o instanceof Coord) {
				return this.row == ((Coord)o).row && this.col == ((Coord)o).col; 
			}
			return false;
		}
	}	
	
	private void checkOver(Move move, int count) throws GameOverException {

		int _row = move.getRow();
		int _col = move.getCol();
		Team team = move.getTeam();
		Queue<Coord> q = new LinkedList<Coord>(); 
		Collection<Coord> coord = new ArrayList<Coord>();
		q.add(new Coord(_row,_col));
		while(q.size()>0) {
			Coord c = q.poll();
			int row = c.getRow();
			int col = c.getCol();
			coord.add(c);
			if(field.getTeam(row-1, col)==team && !coord.contains(new Coord(row-1, col)))
				q.add(new Coord(row-1,col));
			if(field.getTeam(row+1, col)==team && !coord.contains(new Coord(row+1, col)))
				q.add(new Coord(row+1,col));
		}
		if(coord.size()>=5) {
			over = true;
			throw new GameOverException(team);
		}
		
		coord.clear();
		q.add(new Coord(_row,_col));
		while(q.size()>0) {
			Coord c = q.poll();
			int row = c.getRow();
			int col = c.getCol();
			coord.add(c);
			if(field.getTeam(row, col-1)==team && !coord.contains(new Coord(row, col-1)))
				q.add(new Coord(row,col-1));
			if(field.getTeam(row, col+1)==team && !coord.contains(new Coord(row, col+1)))
				q.add(new Coord(row,col+1));
		}
		if(coord.size()>=5) {
			over = true;
			throw new GameOverException(team);
		}

		coord.clear();
		q.add(new Coord(_row,_col));
		while(q.size()>0) {
			Coord c = q.poll();
			int row = c.getRow();
			int col = c.getCol();
			coord.add(c);
			if(field.getTeam(row-1, col-1)==team && !coord.contains(new Coord(row-1, col-1)))
				q.add(new Coord(row-1,col-1));
			if(field.getTeam(row+1, col+1)==team && !coord.contains(new Coord(row+1, col+1)))
				q.add(new Coord(row+1,col+1));
		}
		if(coord.size()>=5) {
			over = true;
			throw new GameOverException(team);
		}

		coord.clear();
		q.add(new Coord(_row,_col));
		while(q.size()>0) {
			Coord c = q.poll();
			int row = c.getRow();
			int col = c.getCol();
			coord.add(c);
			if(field.getTeam(row+1, col-1)==team && !coord.contains(new Coord(row+1, col-1)))
				q.add(new Coord(row+1,col-1));
			if(field.getTeam(row-1, col+1)==team && !coord.contains(new Coord(row-1, col+1)))
				q.add(new Coord(row-1,col+1));
		}
		if(coord.size()>=5) {
			over = true;
			throw new GameOverException(team);
		}

	}

	@Override
	synchronized public Field getField() {
		return field;
	}

	@Override
	synchronized public Move getLastMove(Team team) throws IllegalStateException {
		int last = moves.size()-1;
		if(last<0)
			throw new IllegalStateException("Еще никто не ходил");
		Move move = null;
		for(;last>=0;--last) {
			move = moves.get(last);
			if(move.getTeam() == team)
				break;
		}
		if(move.getTeam() != team)
			throw new IllegalStateException("Игрок не ходил");
		
		return move;
	}

	@Override
	synchronized public void addPlayer(String name, Team team) {
		users.put(team, name);		
	}

	@Override
	synchronized public Transport getTransport() {
		return transport;
	}

	@Override
	synchronized public boolean isAvail(Team team) {
		return !users.containsKey(team);
	}

	@Override
	public void loginPlayer(String name, Team team) {
		if(!users.containsKey(team)) 
			throw new IllegalStateException("Данный тип пользователя не зарегистрирован");
		if(!users.get(team).equals(name)) 
			throw new IllegalStateException("Данный полльзователь не зарегистрирован");
		logged_users.put(name, true);
		if(logged_users.size() == 2)
			start();
	}

	@Override
	public CurrentMove getCurrentMove() {
		return currentMove;
	}

	@Override
	public boolean isStarted() {
		return started ;
	}

	@Override
	public void start() {
		Date now = new Date(); 
		currentMove = new CurrentMoveImpl(Team.X, new Date(now.getTime() + reflex));
		started = true;
	}

	@Override
	public boolean isOver() {
		return over;
	}

	@Override
	public String getApponentName(Team team) {
		return users.get(team);
	}

}
